﻿#pragma once

#include "Lisp.h"
#include "RefCounted.h"
#include "Util.h"

#include <string>
#include <vector>
#include <memory>
#include <cassert>
#include <cstdint>
#include <functional>

namespace Lisp
{
  class ValType : public RefCounted
  {
  public:
    ValType() {};
    ValType(ValPtr meta) : m_meta(meta) {};
    virtual ~ValType() {};

    virtual std::string ToString(bool readably = true) const { return ""; };
    virtual bool Equals(ValType *val) const;

    virtual ValPtr Eval(EnvPtr env);
    virtual bool IsTrue() const;

    ValPtr Meta() const;
    ValPtr WithMeta(ValPtr meta) const;
    virtual ValPtr DoWithMeta(ValPtr meta) const = 0;

  protected:
    ValPtr m_meta;
  };

  template <class T>
  T *ValueCast(ValPtr obj, const char *typeName)
  {
    T *dest = dynamic_cast<T *>(obj.get());
    LISP_CHECK(dest != NULL, "%s is not a %s", obj->ToString(true).c_str(), typeName);
    return dest;
  }

#define VALUE_CAST(Type, Value) Lisp::ValueCast<Type>(Value, #Type)
#define DYNAMIC_CAST(Type, Value) (dynamic_cast<Type *>((Value).get()))
#define STATIC_CAST(Type, Value) (static_cast<Type *>((Value).get()))

#define WITH_META(Type) \
    virtual ValPtr DoWithMeta(ValPtr meta) const { \
        return new Type(*this, meta); \
    } \

  class ConstantType : public ValType
  {
  public:
    ConstantType(const std::string &value) : m_value(value) {};
    ConstantType(const ConstantType &that, ValPtr meta)
        : ValType(meta), m_value(that.m_value) {};
    virtual ~ConstantType() {};

    virtual std::string ToString(bool readably = true) const override;
    virtual bool IsTrue() const;

    WITH_META(ConstantType);

  protected:
    const std::string m_value;
  };

  class IntegerType : public ValType
  {
  public:
    IntegerType(int64_t value) : m_value(value) {};
    IntegerType(const IntegerType &that, ValPtr meta)
        : ValType(meta), m_value(that.m_value) {}
    virtual ~IntegerType() {};

    virtual std::string ToString(bool readably = true) const override;
    virtual bool Equals(ValType *val) const override;

    virtual ValPtr Eval(EnvPtr env);
    virtual bool IsTrue() const;

    int64_t Value() const { return m_value; }

    WITH_META(IntegerType);

  private:
    const int64_t m_value;
  };

  class StringBaseType : public ValType
  {
  public:
    StringBaseType(const std::string &token) : m_value(token) {};
    StringBaseType(const StringBaseType &that, ValPtr meta)
        : ValType(meta), m_value(that.m_value) {}
    virtual ~StringBaseType() {};

    virtual std::string ToString(bool readably = true) const override;
    virtual bool Equals(ValType *val) const override;

    std::string Value() const { return m_value; }

  protected:
    const std::string m_value;
  };

  class SymbolType : public StringBaseType
  {
  public:
    SymbolType(const std::string &token) : StringBaseType(token) {};
    SymbolType(const SymbolType &that, ValPtr meta)
        : StringBaseType(that, meta) {}
    virtual ~SymbolType() {};

    virtual std::string ToString(bool readably = true) const override;
    virtual bool Equals(ValType *val) const override;

    virtual ValPtr Eval(EnvPtr env);

    WITH_META(SymbolType);
  };

  class StringType : public StringBaseType
  {
  public:
    StringType(const std::string &token)
        : StringBaseType(token) {};
    StringType(const StringType &that, ValPtr meta)
        : StringBaseType(that, meta) {}
    virtual ~StringType() {};

    virtual std::string ToString(bool readably = true) const override;
    virtual bool Equals(ValType *val) const override;

    std::string EscapedValue() const;
    size_t Size() const { return m_value.size(); }

    WITH_META(StringType);
  };

  class KeywordType : public StringBaseType
  {
  public:
    KeywordType(const std::string &token)
        : StringBaseType(token) {};
    KeywordType(const KeywordType &that, ValPtr meta)
        : StringBaseType(that, meta) {}
    virtual ~KeywordType() {};

    virtual bool Equals(ValType *val) const override;

    WITH_META(KeywordType);
  };

  using ValIter = std::vector<ValPtr>::iterator;

  class SequenceType : public ValType
  {
  public:
    SequenceType(ValVec *list) : m_list(list) {};
    SequenceType(const ValIter &begin, const ValIter &end) : m_list(new ValVec(begin, end)) {};
    SequenceType(const SequenceType &that, ValPtr meta)
        : ValType(meta), m_list(that.m_list) {}
    virtual ~SequenceType();

    virtual std::string ToString(bool readably = true) const override;
    virtual bool Equals(ValType *val) const override;

    virtual ValVec *EvalItems(EnvPtr env);

    virtual bool IsTrue() const;

    size_t Size() const { return m_list->size(); }
    ValPtr At(size_t index) const { return m_list->at(index); };
    bool IsEmpty() const { return m_list->empty(); }

    ValIter begin() const { return m_list->begin(); }
    ValIter end() const { return m_list->end(); }

    virtual ValPtr Conj(ValIter argsBegin, ValIter argsEnd) const = 0;

    ValPtr First() const;
    virtual ValPtr Rest() const;

  protected:
    ValVec *const m_list;
  };

  class ListType : public SequenceType
  {
  public:
    ListType(ValVec *list) : SequenceType(list) {};
    ListType(const ValIter &begin, const ValIter &end) : SequenceType(begin, end) {};
    ListType(const ListType &that, ValPtr meta)
        : SequenceType(that, meta) {}
    virtual ~ListType() {};

    virtual std::string ToString(bool readably = true) const override;

    virtual ValPtr Eval(EnvPtr env) override;

    virtual ValPtr Conj(ValIter argsBegin, ValIter argsEnd) const override;
  
    WITH_META(ListType);
  };

  class VectorType : public SequenceType
  {
  public:
    VectorType(ValVec *list) : SequenceType(list) {};
    VectorType(const ValIter &begin, const ValIter &end) : SequenceType(begin, end) {};
    VectorType(const VectorType &that, ValPtr meta)
        : SequenceType(that, meta) {}
    virtual ~VectorType() {};

    virtual std::string ToString(bool readably = true) const override;

    virtual ValPtr Eval(EnvPtr env) override;

    virtual ValPtr Conj(ValIter argsBegin, ValIter argsEnd) const override;

    WITH_META(VectorType);
  };

  class HashType : public ValType
  {
  public:
    using Map = std::unordered_map<std::string, ValPtr>;

    HashType(ValVec *pairs, bool isEvaluated = false);
    HashType(const HashType::Map &map)
        : m_map(map), m_isEvaluated(true) {};
    HashType(ValIter begin, ValIter end, bool isEvaluated = false);
    HashType(const HashType &that, ValPtr meta)
        : ValType(meta), m_map(that.m_map), m_isEvaluated(that.m_isEvaluated) {}
    virtual ~HashType() {};

    virtual std::string ToString(bool readably = true) const override;
    virtual bool Equals(ValType *val) const override;
    virtual ValPtr Eval(EnvPtr env) override;

    ValPtr Assoc(ValIter argsBegin, ValIter argsEnd) const;
    ValPtr Dissoc(ValIter argsBegin, ValIter argsEnd) const;
    ValPtr Get(ValPtr key) const;
    ValPtr Keys() const;
    ValPtr Values() const;
    bool Contains(ValPtr key) const;

    WITH_META(HashType);

  private:
    const Map m_map;
    const bool m_isEvaluated;
  };

  class ApplicableType : public ValType
  {
  public:
    ApplicableType() {};
    ApplicableType(ValPtr meta)
        : ValType(meta) {};
    virtual ~ApplicableType() {};

    virtual ValPtr Apply(ListPtr args) const = 0;
  };

  class BuiltInFuncType : public ApplicableType
  {
  public:
    typedef ValPtr(ApplyFunc)(const std::string &name, ListPtr args);
    typedef ApplyFunc *ApplyFuncPtr;

    BuiltInFuncType(const std::string &name, ApplyFuncPtr func) : m_name(name), m_func(func) {};
    BuiltInFuncType(const BuiltInFuncType &that, ValPtr meta)
        : ApplicableType(meta), m_name(that.m_name), m_func(that.m_func) {};
    virtual ~BuiltInFuncType() {};

    virtual std::string ToString(bool readably = true) const override;
    virtual bool Equals(ValType *val) const override;

    virtual ValPtr Apply(ListPtr args) const;
    ApplyFuncPtr Func() const { return m_func; }

    WITH_META(BuiltInFuncType);

  private:
    const std::string m_name;
    const ApplyFuncPtr m_func;
  };

  class LambdaType : public ApplicableType
  {
  public:
    LambdaType(const std::vector<std::string> &bindngs, ValPtr body, EnvPtr env);
    LambdaType(const LambdaType &that, ValPtr meta);
    LambdaType(const LambdaType &that, bool isMacro);
    virtual ~LambdaType();

    virtual std::string ToString(bool readably = true) const override;
    virtual bool Equals(ValType *val) const override;

    virtual ValPtr Apply(ListPtr args) const;

    ValPtr GetBody() const { return m_body; }
    EnvPtr MakeEnv(ListPtr args) const;

    bool IsMacro() const { return m_isMacro; }

    WITH_META(LambdaType);

  private:
    const std::vector<std::string> m_bindings;
    const ValPtr m_body;
    const EnvPtr m_env;
    const bool m_isMacro;
  };

  class AtomType : public ValType
  {
  public:
    AtomType(ValPtr value)
        : m_value(value) {};
    AtomType(const AtomType &that, ValPtr meta)
        : ValType(meta), m_value(that.m_value) {};
    virtual ~AtomType() {};

    virtual std::string ToString(bool readably = true) const override;
    virtual bool Equals(ValType *val) const override;

    ValPtr Deref() const { return m_value; }
    ValPtr Reset(ValPtr value) { return m_value = value; }

    WITH_META(AtomType);

  private:
    ValPtr m_value;
  };

  ValPtr AtomVal(ValPtr value);
  ValPtr NilVal();
  ValPtr TrueVal();
  ValPtr FalseVal();
  ValPtr BoolVal(bool value);
  ValPtr IntegerVal(int64_t value);
  ValPtr IntegerVal(const std::string &token);
  ValPtr StringVal(const std::string &token);
  ValPtr SymbolVal(const std::string &token);
  ValPtr ListVal(ValVec *list);
  ValPtr ListVal(std::initializer_list<ValPtr> list);
  ValPtr ListVal(ValIter begin, ValIter end);
  ValPtr VectorVal(ValVec *list);
  ValPtr VectorVal(std::initializer_list<ValPtr> list);
  ValPtr VectorVal(ValIter begin, ValIter end);
  ValPtr HashVal(ValVec *pairs, bool isEvaluated);
  ValPtr HashVal(std::initializer_list<ValPtr> pairs, bool isEvaluated);
  ValPtr HashVal(ValIter begin, ValIter end, bool isEvaluated);
  ValPtr HashVal(const HashType::Map &map);
  ValPtr BuiltInVal(const std::string &name, BuiltInFuncType::ApplyFuncPtr func);
  ValPtr LambdaVal(const std::vector<std::string> &params, ValPtr body, EnvPtr env);
  ValPtr MacroVal(const LambdaType &lambda);
  ValPtr KeywordVal(const std::string &token);
}